Audacity 3.2.0
SqliteSampleBlock.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5SqliteSampleBlock.cpp
6
7Paul Licameli -- split from SampleBlock.cpp and SampleBlock.h
8
9**********************************************************************/
10
11#include <float.h>
12#include <sqlite3.h>
13
14#include "BasicUI.h"
15#include "DBConnection.h"
16#include "ProjectFileIO.h"
17#include "SampleFormat.h"
19#include "XMLTagHandler.h"
20
21#include "SampleBlock.h" // to inherit
22#include "UndoManager.h"
23#include "WaveTrack.h"
24
25#include "SentryHelper.h"
26#include <wx/log.h>
27
28#include <mutex>
29
31
33class SqliteSampleBlock final : public SampleBlock
34{
35public:
36 BlockSampleView GetFloatSampleView(bool mayThrow) override;
37
38private:
39 std::weak_ptr<std::vector<float>> mCache;
40 std::mutex mCacheMutex;
41
42public:
43 explicit SqliteSampleBlock(
44 const std::shared_ptr<SqliteSampleBlockFactory> &pFactory);
45 ~SqliteSampleBlock() override;
46
47 void CloseLock() noexcept override;
48
49 void SetSamples(
50 constSamplePtr src, size_t numsamples, sampleFormat srcformat);
51
53 using Sizes = std::pair< size_t, size_t >;
54 void Commit(Sizes sizes);
55
56 void Delete();
57
58 SampleBlockID GetBlockID() const override;
59
60 size_t DoGetSamples(samplePtr dest,
61 sampleFormat destformat,
62 size_t sampleoffset,
63 size_t numsamples) override;
65 size_t GetSampleCount() const override;
66
67 bool GetSummary256(float *dest, size_t frameoffset, size_t numframes) override;
68 bool GetSummary64k(float *dest, size_t frameoffset, size_t numframes) override;
69 double GetSumMin() const;
70 double GetSumMax() const;
71 double GetSumRms() const;
72
74 MinMaxRMS DoGetMinMaxRMS(size_t start, size_t len) override;
75
77 MinMaxRMS DoGetMinMaxRMS() const override;
78
79 size_t GetSpaceUsage() const override;
80 void SaveXML(XMLWriter &xmlFile) override;
81
82private:
83 bool IsSilent() const { return mBlockID <= 0; }
84 void Load(SampleBlockID sbid);
85 bool GetSummary(float *dest,
86 size_t frameoffset,
87 size_t numframes,
89 const char *sql);
90 size_t GetBlob(void *dest,
91 sampleFormat destformat,
92 sqlite3_stmt *stmt,
93 sampleFormat srcformat,
94 size_t srcoffset,
95 size_t srcbytes);
96
97 enum {
98 fields = 3, /* min, max, rms */
99 bytesPerFrame = fields * sizeof(float),
100 };
101 Sizes SetSizes( size_t numsamples, sampleFormat srcformat );
102 void CalcSummary(Sizes sizes);
103
104private:
106
107 DBConnection *Conn() const;
108 sqlite3 *DB() const
109 {
110 return Conn()->DB();
111 }
112
114
115 const std::shared_ptr<SqliteSampleBlockFactory> mpFactory;
116 bool mValid{ false };
117 bool mLocked = false;
118
120
125
128 double mSumMin;
129 double mSumMax;
130 double mSumRms;
131
132#if defined(WORDS_BIGENDIAN)
133#error All sample block data is little endian...big endian not yet supported
134#endif
135};
136
137// Silent blocks use nonpositive id values to encode a length
138// and don't occupy any rows in the database; share blocks for repeatedly
139// used length values
140static std::map< SampleBlockID, std::shared_ptr<SqliteSampleBlock> >
142
145 : public SampleBlockFactory
146 , public std::enable_shared_from_this<SqliteSampleBlockFactory>
147{
148public:
150
152
154
156 size_t numsamples,
157 sampleFormat srcformat) override;
158
160 size_t numsamples,
161 sampleFormat srcformat) override;
162
164 sampleFormat srcformat,
165 const AttributesList &attrs) override;
166
167private:
168 void OnBeginPurge(size_t begin, size_t end);
169 void OnEndPurge();
170
172
175 std::optional<SampleBlock::DeletionCallback::Scope> mScope;
176 const std::shared_ptr<ConnectionPtr> mppConnection;
177
178 // Track all blocks that this factory has created, but don't control
179 // their lifetimes (so use weak_ptr)
180 // (Must also use weak pointers because the blocks have shared pointers
181 // to the factory and we can't have a leaky cycle of shared pointers)
183 std::map< SampleBlockID, std::weak_ptr< SqliteSampleBlock > >;
185};
186
188 : mProject{ project }
189 , mppConnection{ ConnectionPtr::Get(project).shared_from_this() }
190{
192 .Subscribe([this](UndoRedoMessage message){
193 switch (message.type) {
194 case UndoRedoMessage::BeginPurge:
195 return OnBeginPurge(message.begin, message.end);
196 case UndoRedoMessage::EndPurge:
197 return OnEndPurge();
198 default:
199 return;
200 }
201 });
202}
203
205
207 constSamplePtr src, size_t numsamples, sampleFormat srcformat )
208{
209 auto sb = std::make_shared<SqliteSampleBlock>(shared_from_this());
210 sb->SetSamples(src, numsamples, srcformat);
211 // block id has now been assigned
212 mAllBlocks[ sb->GetBlockID() ] = sb;
213 return sb;
214}
215
217{
218 SampleBlockIDs result;
219 for (auto end = mAllBlocks.end(), it = mAllBlocks.begin(); it != end;) {
220 if (it->second.expired())
221 // Tighten up the map
222 it = mAllBlocks.erase(it);
223 else {
224 result.insert( it->first );
225 ++it;
226 }
227 }
228 return result;
229}
230
232 size_t numsamples, sampleFormat )
233{
234 auto id = -static_cast< SampleBlockID >(numsamples);
235 auto &result = sSilentBlocks[ id ];
236 if ( !result ) {
237 result = std::make_shared<SqliteSampleBlock>(nullptr);
238 result->mBlockID = id;
239
240 // Ignore the supplied sample format
241 result->SetSizes(numsamples, floatSample);
242 result->mValid = true;
243 }
244
245 return result;
246}
247
248
250 sampleFormat srcformat, const AttributesList &attrs )
251{
252 std::shared_ptr<SampleBlock> sb;
253
254 int found = 0;
255
256 // loop through attrs, which is a null-terminated list of attribute-value pairs
257 for (auto pair : attrs)
258 {
259 auto attr = pair.first;
260 auto value = pair.second;
261
262 long long nValue;
263
264 if (attr == "blockid" && value.TryGet(nValue))
265 {
266 if (nValue <= 0) {
267 sb = DoCreateSilent( -nValue, floatSample );
268 }
269 else {
270 // First see if this block id was previously loaded
271 auto &wb = mAllBlocks[ nValue ];
272 auto pb = wb.lock();
273 if (pb)
274 // Reuse the block
275 sb = pb;
276 else {
277 // First sight of this id
278 auto ssb =
279 std::make_shared<SqliteSampleBlock>(shared_from_this());
280 wb = ssb;
281 sb = ssb;
282 ssb->mSampleFormat = srcformat;
283 // This may throw database errors
284 // It initializes the rest of the fields
285 ssb->Load((SampleBlockID) nValue);
286 }
287 }
288 found++;
289 }
290 }
291
292 // Were all attributes found?
293 if (found != 1)
294 {
295 return nullptr;
296 }
297
298 return sb;
299}
300
302{
303 assert(mSampleCount > 0);
304
305 // Double-checked locking.
306 // `weak_ptr::lock()` guarantees atomicity, which is important to make this
307 // work without races.
308 auto cache = mCache.lock();
309 if (cache)
310 return cache;
311 std::lock_guard<std::mutex> lock(mCacheMutex);
312 cache = mCache.lock();
313 if (cache)
314 return cache;
315
316 const auto newCache =
317 std::make_shared<std::vector<float>>(mSampleCount);
318 try {
319 const auto cachedSize = DoGetSamples(
320 reinterpret_cast<samplePtr>(newCache->data()), floatSample, 0,
322 assert(cachedSize == mSampleCount);
323 }
324 catch (...)
325 {
326 if (mayThrow)
327 std::rethrow_exception(std::current_exception());
328 std::fill(newCache->begin(), newCache->end(), 0.f);
329 }
330 mCache = newCache;
331 return newCache;
332}
333
335 const std::shared_ptr<SqliteSampleBlockFactory> &pFactory)
336: mpFactory(pFactory)
337{
339 mSampleBytes = 0;
340 mSampleCount = 0;
341
342 mSumMin = 0.0;
343 mSumMax = 0.0;
344 mSumRms = 0.0;
345}
346
348{
350
351 if (IsSilent()) {
352 // The block object was constructed but failed to Load() or Commit().
353 // Or it's a silent block with no row in the database.
354 // Just let the stack unwind. Don't violate the assertion in
355 // Delete(), which may do odd recursive things in debug builds when it
356 // yields to the UI to put up a dialog, but then dispatches timer
357 // events that try again to finish recording.
358 return;
359 }
360
361 // See ProjectFileIO::Bypass() for a description of mIO.mBypass
362 GuardedCall( [this]{
363 if (!mLocked && !Conn()->ShouldBypass())
364 {
365 // In case Delete throws, don't let an exception escape a destructor,
366 // but we can still enqueue the delayed handler so that an error message
367 // is presented to the user.
368 // The failure in this case may be a less harmful waste of space in the
369 // database, which should not cause aborting of the attempted edit.
370 Delete();
371 }
372 } );
373}
374
376{
377 if (!mpFactory)
378 return nullptr;
379
380 auto &pConnection = mpFactory->mppConnection->mpConnection;
381 if (!pConnection) {
383 {
385 XO("Connection to project file is null"),
386 XO("Warning"),
387 "Error:_Disk_full_or_not_writable"
388 };
389 }
390 return pConnection.get();
391}
392
394{
395 mLocked = true;
396}
397
399{
400 return mBlockID;
401}
402
404{
405 return mSampleFormat;
406}
407
409{
410 return mSampleCount;
411}
412
414 sampleFormat destformat,
415 size_t sampleoffset,
416 size_t numsamples)
417{
418 if (IsSilent()) {
419 auto size = SAMPLE_SIZE(destformat);
420 memset(dest, 0, numsamples * size);
421 return numsamples;
422 }
423
424 // Prepare and cache statement...automatically finalized at DB close
425 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::GetSamples,
426 "SELECT samples FROM sampleblocks WHERE blockid = ?1;");
427
428 return GetBlob(dest,
429 destformat,
430 stmt,
432 sampleoffset * SAMPLE_SIZE(mSampleFormat),
434}
435
437 size_t numsamples,
438 sampleFormat srcformat)
439{
440 auto sizes = SetSizes(numsamples, srcformat);
442 memcpy(mSamples.get(), src, mSampleBytes);
443
444 CalcSummary( sizes );
445
446 Commit( sizes );
447}
448
450 size_t frameoffset,
451 size_t numframes)
452{
453 return GetSummary(dest, frameoffset, numframes, DBConnection::GetSummary256,
454 "SELECT summary256 FROM sampleblocks WHERE blockid = ?1;");
455}
456
458 size_t frameoffset,
459 size_t numframes)
460{
461 return GetSummary(dest, frameoffset, numframes, DBConnection::GetSummary64k,
462 "SELECT summary64k FROM sampleblocks WHERE blockid = ?1;");
463}
464
466 size_t frameoffset,
467 size_t numframes,
469 const char *sql)
470{
471 // Non-throwing, it returns true for success
472 bool silent = IsSilent();
473 if (!silent) {
474 // Not a silent block
475 try {
476 // Prepare and cache statement...automatically finalized at DB close
477 auto stmt = Conn()->Prepare(id, sql);
478 // Note GetBlob returns a size_t, not a bool
479 // REVIEW: An error in GetBlob() will throw an exception.
480 GetBlob(dest,
482 stmt,
484 frameoffset * fields * SAMPLE_SIZE(floatSample),
485 numframes * fields * SAMPLE_SIZE(floatSample));
486 return true;
487 }
488 catch ( const AudacityException & ) {
489 }
490 }
491 memset(dest, 0, 3 * numframes * sizeof( float ));
492 // Return true for success only if we didn't catch
493 return silent;
494}
495
497{
498 return mSumMin;
499}
500
502{
503 return mSumMax;
504}
505
507{
508 return mSumRms;
509}
510
517{
518 if (IsSilent())
519 return {};
520
521 float min = FLT_MAX;
522 float max = -FLT_MAX;
523 float sumsq = 0;
524
525 if (!mValid)
526 {
527 Load(mBlockID);
528 }
529
530 if (start < mSampleCount)
531 {
532 len = std::min(len, mSampleCount - start);
533
534 // TODO: actually use summaries
535 SampleBuffer blockData(len, floatSample);
536 float *samples = (float *) blockData.ptr();
537
538 size_t copied = DoGetSamples((samplePtr) samples, floatSample, start, len);
539 for (size_t i = 0; i < copied; ++i, ++samples)
540 {
541 float sample = *samples;
542
543 if (sample > max)
544 {
545 max = sample;
546 }
547
548 if (sample < min)
549 {
550 min = sample;
551 }
552
553 sumsq += (sample * sample);
554 }
555 }
556
557 return { min, max, (float) sqrt(sumsq / len) };
558}
559
564{
565 return { (float) mSumMin, (float) mSumMax, (float) mSumRms };
566}
567
569{
570 if (IsSilent())
571 return 0;
572 else
574}
575
577 sampleFormat destformat,
578 sqlite3_stmt *stmt,
579 sampleFormat srcformat,
580 size_t srcoffset,
581 size_t srcbytes)
582{
583 auto db = DB();
584
585 wxASSERT(!IsSilent());
586
587 if (!mValid)
588 {
589 Load(mBlockID);
590 }
591
592 int rc;
593 size_t minbytes = 0;
594
595 // Bind statement parameters
596 // Might return SQLITE_MISUSE which means it's our mistake that we violated
597 // preconditions; should return SQL_OK which is 0
598 if (sqlite3_bind_int64(stmt, 1, mBlockID))
599 {
601 "sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
602 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::GetBlob::bind");
603
604 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
605 }
606
607 // Execute the statement
608 rc = sqlite3_step(stmt);
609 if (rc != SQLITE_ROW)
610 {
611 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
612 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::GetBlob::step");
613
614 wxLogDebug(wxT("SqliteSampleBlock::GetBlob - SQLITE error %s"), sqlite3_errmsg(db));
615
616 // Clear statement bindings and rewind statement
617 sqlite3_clear_bindings(stmt);
618 sqlite3_reset(stmt);
619
620 // Just showing the user a simple message, not the library error too
621 // which isn't internationalized
622 // Actually this can lead to 'Could not read from file' error message
623 // but it can also lead to no error message at all and a flat line,
624 // depending on where GetBlob is called from.
625 // The latter can happen when repainting the screen.
626 // That possibly happens on a very slow machine. Possibly that's the
627 // right trade off when a machine can't keep up?
628 // ANSWER-ME: Do we always report an error when we should here?
629 Conn()->ThrowException( false );
630 }
631
632 // Retrieve returned data
633 samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0);
634 size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0);
635
636 srcoffset = std::min(srcoffset, blobbytes);
637 minbytes = std::min(srcbytes, blobbytes - srcoffset);
638
639 if (srcoffset != 0)
640 {
641 srcoffset += 0;
642 }
643
644 /*
645 Will dithering happen in CopySamples? Answering this as of 3.0.3 by
646 examining all uses.
647
648 As this function is called from GetSummary, no, because destination format
649 is float.
650
651 There is only one other call to this function, in DoGetSamples. At one
652 call to that function, in DoGetMinMaxRMS, again format is float always.
653
654 There is only one other call to DoGetSamples, in SampleBlock::GetSamples().
655 In one call to that function, in WaveformView.cpp, again format is float.
656
657 That leaves two calls in Sequence.cpp. One of those can be proved to be
658 used only in copy and paste operations, always supplying the same sample
659 format as the samples were stored in, therefore no dither.
660
661 That leaves uses of Sequence::Read(). There are uses of Read() in internal
662 operations also easily shown to use only the saved format, and
663 GetWaveDisplay() always reads as float.
664
665 The remaining use of Sequence::Read() is in Sequence::Get(). That is used
666 by WaveClip::Resample(), always fetching float. It is also used in
667 WaveClip::GetSamples().
668
669 There is only one use of that function not always fetching float, in
670 WaveTrack::Get().
671
672 It can be shown that the only paths to WaveTrack::Get() not specifying
673 floatSample are in Benchmark, which is only a diagnostic test, and there
674 the sample format is the same as what the track was constructed with.
675
676 Therefore, no dithering even there!
677 */
678 wxASSERT(destformat == floatSample || destformat == srcformat);
679
680 CopySamples(src + srcoffset,
681 srcformat,
682 (samplePtr) dest,
683 destformat,
684 minbytes / SAMPLE_SIZE(srcformat));
685
686 dest = ((samplePtr) dest) + minbytes;
687
688 if (srcbytes - minbytes)
689 {
690 memset(dest, 0, srcbytes - minbytes);
691 }
692
693 // Clear statement bindings and rewind statement
694 sqlite3_clear_bindings(stmt);
695 sqlite3_reset(stmt);
696
697 return srcbytes;
698}
699
701{
702 auto db = DB();
703 int rc;
704
705 wxASSERT(sbid > 0);
706
707 mValid = false;
708 mSampleCount = 0;
709 mSampleBytes = 0;
710 mSumMin = FLT_MAX;
711 mSumMax = -FLT_MAX;
712 mSumMin = 0.0;
713
714 // Prepare and cache statement...automatically finalized at DB close
715 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::LoadSampleBlock,
716 "SELECT sampleformat, summin, summax, sumrms,"
717 " length(samples)"
718 " FROM sampleblocks WHERE blockid = ?1;");
719
720 // Bind statement parameters
721 // Might return SQLITE_MISUSE which means it's our mistake that we violated
722 // preconditions; should return SQL_OK which is 0
723 if (sqlite3_bind_int64(stmt, 1, sbid))
724 {
725
726 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
727 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Load::bind");
728
729 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
730 }
731
732 // Execute the statement
733 rc = sqlite3_step(stmt);
734 if (rc != SQLITE_ROW)
735 {
736
737 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
738 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Load::step");
739
740
741 wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db));
742
743 // Clear statement bindings and rewind statement
744 sqlite3_clear_bindings(stmt);
745 sqlite3_reset(stmt);
746
747 // Just showing the user a simple message, not the library error too
748 // which isn't internationalized
749 Conn()->ThrowException( false );
750 }
751
752 // Retrieve returned data
753 mBlockID = sbid;
754 mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0);
755 mSumMin = sqlite3_column_double(stmt, 1);
756 mSumMax = sqlite3_column_double(stmt, 2);
757 mSumRms = sqlite3_column_double(stmt, 3);
758 mSampleBytes = sqlite3_column_int(stmt, 4);
760
761 // Clear statement bindings and rewind statement
762 sqlite3_clear_bindings(stmt);
763 sqlite3_reset(stmt);
764
765 mValid = true;
766}
767
769{
770 const auto mSummary256Bytes = sizes.first;
771 const auto mSummary64kBytes = sizes.second;
772
773 auto db = DB();
774 int rc;
775
776 // Prepare and cache statement...automatically finalized at DB close
777 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::InsertSampleBlock,
778 "INSERT INTO sampleblocks (sampleformat, summin, summax, sumrms,"
779 " summary256, summary64k, samples)"
780 " VALUES(?1,?2,?3,?4,?5,?6,?7);");
781
782 // Bind statement parameters
783 // Might return SQLITE_MISUSE which means it's our mistake that we violated
784 // preconditions; should return SQL_OK which is 0
785 if (sqlite3_bind_int(stmt, 1, static_cast<int>(mSampleFormat)) ||
786 sqlite3_bind_double(stmt, 2, mSumMin) ||
787 sqlite3_bind_double(stmt, 3, mSumMax) ||
788 sqlite3_bind_double(stmt, 4, mSumRms) ||
789 sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC) ||
790 sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC) ||
791 sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC))
792 {
793
795 "sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
796 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Commit::bind");
797
798
799 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
800 }
801
802 // Execute the statement
803 rc = sqlite3_step(stmt);
804 if (rc != SQLITE_DONE)
805 {
806 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
807 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Commit::step");
808
809 wxLogDebug(wxT("SqliteSampleBlock::Commit - SQLITE error %s"), sqlite3_errmsg(db));
810
811 // Clear statement bindings and rewind statement
812 sqlite3_clear_bindings(stmt);
813 sqlite3_reset(stmt);
814
815 // Just showing the user a simple message, not the library error too
816 // which isn't internationalized
817 Conn()->ThrowException( true );
818 }
819
820 // Retrieve returned data
821 mBlockID = sqlite3_last_insert_rowid(db);
822
823 // Reset local arrays
824 mSamples.reset();
825 mSummary256.reset();
826 mSummary64k.reset();
827 {
828 std::lock_guard<std::mutex> lock(mCacheMutex);
829 mCache.reset();
830 }
831
832 // Clear statement bindings and rewind statement
833 sqlite3_clear_bindings(stmt);
834 sqlite3_reset(stmt);
835
836 mValid = true;
837}
838
840{
841 auto db = DB();
842 int rc;
843
844 wxASSERT(!IsSilent());
845
846 // Prepare and cache statement...automatically finalized at DB close
847 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::DeleteSampleBlock,
848 "DELETE FROM sampleblocks WHERE blockid = ?1;");
849
850 // Bind statement parameters
851 // Might return SQLITE_MISUSE which means it's our mistake that we violated
852 // preconditions; should return SQL_OK which is 0
853 if (sqlite3_bind_int64(stmt, 1, mBlockID))
854 {
856 "sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
857 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Delete::bind");
858
859 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
860 }
861
862 // Execute the statement
863 rc = sqlite3_step(stmt);
864 if (rc != SQLITE_DONE)
865 {
866 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
867 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Delete::step");
868
869 wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db));
870
871 // Clear statement bindings and rewind statement
872 sqlite3_clear_bindings(stmt);
873 sqlite3_reset(stmt);
874
875 // Just showing the user a simple message, not the library error too
876 // which isn't internationalized
877 Conn()->ThrowException( true );
878 }
879
880 // Clear statement bindings and rewind statement
881 sqlite3_clear_bindings(stmt);
882 sqlite3_reset(stmt);
883}
884
886{
887 xmlFile.WriteAttr(wxT("blockid"), mBlockID);
888}
889
891 size_t numsamples, sampleFormat srcformat ) -> Sizes
892{
893 mSampleFormat = srcformat;
894 mSampleCount = numsamples;
895 mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
896
897 int frames64k = (mSampleCount + 65535) / 65536;
898 int frames256 = frames64k * 256;
899 return { frames256 * bytesPerFrame, frames64k * bytesPerFrame };
900}
901
908{
909 const auto mSummary256Bytes = sizes.first;
910 const auto mSummary64kBytes = sizes.second;
911
912 Floats samplebuffer;
913 float *samples;
914
916 {
917 samples = (float *) mSamples.get();
918 }
919 else
920 {
921 samplebuffer.reinit((unsigned) mSampleCount);
923 samplebuffer.get(), mSampleCount);
924 samples = samplebuffer.get();
925 }
926
927 mSummary256.reinit(mSummary256Bytes);
928 mSummary64k.reinit(mSummary64kBytes);
929
930 float *summary256 = (float *) mSummary256.get();
931 float *summary64k = (float *) mSummary64k.get();
932
933 float min;
934 float max;
935 float sumsq;
936 double totalSquares = 0.0;
937 double fraction = 0.0;
938
939 // Recalc 256 summaries
940 int sumLen = (mSampleCount + 255) / 256;
941 int summaries = 256;
942
943 for (int i = 0; i < sumLen; ++i)
944 {
945 min = samples[i * 256];
946 max = samples[i * 256];
947 sumsq = min * min;
948
949 int jcount = 256;
950 if (jcount > mSampleCount - i * 256)
951 {
952 jcount = mSampleCount - i * 256;
953 fraction = 1.0 - (jcount / 256.0);
954 }
955
956 for (int j = 1; j < jcount; ++j)
957 {
958 float f1 = samples[i * 256 + j];
959 sumsq += f1 * f1;
960
961 if (f1 < min)
962 {
963 min = f1;
964 }
965 else if (f1 > max)
966 {
967 max = f1;
968 }
969 }
970
971 totalSquares += sumsq;
972
973 summary256[i * fields] = min;
974 summary256[i * fields + 1] = max;
975 // The rms is correct, but this may be for less than 256 samples in last loop.
976 summary256[i * fields + 2] = (float) sqrt(sumsq / jcount);
977 }
978
979 for (int i = sumLen, frames256 = mSummary256Bytes / bytesPerFrame;
980 i < frames256; ++i)
981 {
982 // filling in the remaining bits with non-harming/contributing values
983 // rms values are not "non-harming", so keep count of them:
984 summaries--;
985 summary256[i * fields] = FLT_MAX; // min
986 summary256[i * fields + 1] = -FLT_MAX; // max
987 summary256[i * fields + 2] = 0.0f; // rms
988 }
989
990 // Calculate now while we can do it accurately
991 mSumRms = sqrt(totalSquares / mSampleCount);
992
993 // Recalc 64K summaries
994 sumLen = (mSampleCount + 65535) / 65536;
995
996 for (int i = 0; i < sumLen; ++i)
997 {
998 min = summary256[3 * i * 256];
999 max = summary256[3 * i * 256 + 1];
1000 sumsq = summary256[3 * i * 256 + 2];
1001 sumsq *= sumsq;
1002
1003 for (int j = 1; j < 256; ++j)
1004 {
1005 // we can overflow the useful summary256 values here, but have put
1006 // non-harmful values in them
1007 if (summary256[3 * (i * 256 + j)] < min)
1008 {
1009 min = summary256[3 * (i * 256 + j)];
1010 }
1011
1012 if (summary256[3 * (i * 256 + j) + 1] > max)
1013 {
1014 max = summary256[3 * (i * 256 + j) + 1];
1015 }
1016
1017 float r1 = summary256[3 * (i * 256 + j) + 2];
1018 sumsq += r1 * r1;
1019 }
1020
1021 double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction;
1022 float rms = (float) sqrt(sumsq / denom);
1023
1024 summary64k[i * fields] = min;
1025 summary64k[i * fields + 1] = max;
1026 summary64k[i * fields + 2] = rms;
1027 }
1028
1029 for (int i = sumLen, frames64k = mSummary64kBytes / bytesPerFrame;
1030 i < frames64k; ++i)
1031 {
1032 wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here?
1033
1034 summary64k[i * fields] = 0.0f; // probably should be FLT_MAX, need a test case
1035 summary64k[i * fields + 1] = 0.0f; // probably should be -FLT_MAX, need a test case
1036 summary64k[i * fields + 2] = 0.0f; // just padding
1037 }
1038
1039 // Recalc block-level summary (mRMS already calculated)
1040 min = summary64k[0];
1041 max = summary64k[1];
1042
1043 for (int i = 1; i < sumLen; ++i)
1044 {
1045 if (summary64k[i * fields] < min)
1046 {
1047 min = summary64k[i * fields];
1048 }
1049
1050 if (summary64k[i * fields + 1] > max)
1051 {
1052 max = summary64k[i * fields + 1];
1053 }
1054 }
1055
1056 mSumMin = min;
1057 mSumMax = max;
1058}
1059
1061
1063 AudacityProject &project, size_t begin, size_t end)
1064{
1066
1067 // Collect ids that survive
1068 SampleBlockIDSet wontDelete;
1069 auto f = [&](const UndoStackElem &elem) {
1070 if (auto pTracks = TrackList::FindUndoTracks(elem))
1071 InspectBlocks(*pTracks, {}, &wontDelete);
1072 };
1073 manager.VisitStates(f, 0, begin);
1074 manager.VisitStates(f, end, manager.GetNumStates());
1075 if (const auto saved = manager.GetSavedState(); saved >= 0)
1076 manager.VisitStates(f, saved, saved + 1);
1077 InspectBlocks(TrackList::Get(project), {}, &wontDelete);
1078
1079 // Collect ids that won't survive (and are not negative pseudo ids)
1080 SampleBlockIDSet seen, mayDelete;
1081 manager.VisitStates([&](const UndoStackElem &elem) {
1082 if (auto pTracks = TrackList::FindUndoTracks(elem)) {
1083 InspectBlocks(*pTracks,
1084 [&](const SampleBlock &block){
1085 auto id = block.GetBlockID();
1086 if (id > 0 && !wontDelete.count(id))
1087 mayDelete.insert(id);
1088 },
1089 &seen
1090 );
1091 }
1092 }, begin, end);
1093 return mayDelete.size();
1094}
1095
1097{
1098 // Install a callback function that updates a progress indicator
1099 using namespace BasicUI;
1100
1101 //Avoid showing dialog to the user if purge operation
1102 //does not take much time, as it will resign focus from main window
1103 //but dialog itself may not be presented to the user at all.
1104 //On MacOS 13 (bug #3975) focus isn't restored in that case.
1105 constexpr auto ProgressDialogShowDelay = std::chrono::milliseconds (200);
1106 const auto nToDelete = EstimateRemovedBlocks(mProject, begin, end);
1107 if(nToDelete == 0)
1108 return;
1109 auto purgeStartTime = std::chrono::system_clock::now();
1110 std::shared_ptr<ProgressDialog> progressDialog;
1111 mScope.emplace([=, nDeleted = 0](auto&) mutable {
1112 ++nDeleted;
1113 if(!progressDialog)
1114 {
1115 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
1116 std::chrono::system_clock::now() - purgeStartTime);
1117 if(elapsed >= ProgressDialogShowDelay)
1118 progressDialog = MakeProgress(XO("Progress"), XO("Discarding undo/redo history"), 0);
1119 }
1120 else
1121 progressDialog->Poll(nDeleted, nToDelete);
1122 });
1123}
1124
1126{
1127 mScope.reset();
1128}
1129
1130// Inject our database implementation at startup
1132{
1133 return std::make_shared<SqliteSampleBlockFactory>( project );
1134} };
wxT("CloseDown"))
@ Internal
Indicates internal failure from Audacity.
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
An audio segment is either a whole clip or the silence between clips. Views allow shared references t...
std::shared_ptr< std::vector< float > > BlockSampleView
Toolkit-neutral facade for basic user interface services.
int min(int a, int b)
Declare DBConnection, which maintains database connection and associated status and background thread...
XO("Cut/Copy/Paste")
long long SampleBlockID
Definition: ProjectFileIO.h:43
static const AttachedProjectObjects::RegisteredFactory manager
std::shared_ptr< SampleBlock > SampleBlockPtr
Definition: SampleBlock.h:28
void SamplesToFloats(constSamplePtr src, sampleFormat srcFormat, float *dst, size_t len, size_t srcStride, size_t dstStride)
Copy samples from any format into the widest format, which is 32 bit float, with no dithering.
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
char * samplePtr
Definition: SampleFormat.h:57
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const char * constSamplePtr
Definition: SampleFormat.h:58
#define ADD_EXCEPTION_CONTEXT(name, value)
Definition: SentryHelper.h:21
static size_t EstimateRemovedBlocks(AudacityProject &project, size_t begin, size_t end)
Just to find a denominator for a progress indicator.
static std::map< SampleBlockID, std::shared_ptr< SqliteSampleBlock > > sSilentBlocks
static SampleBlockFactory::Factory::Scope scope
const auto project
void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs)
Definition: WaveTrack.cpp:4459
std::unordered_set< SampleBlockID > SampleBlockIDSet
Definition: WaveTrack.h:1231
int id
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:57
Base class for exceptions specially processed by the application.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
void ThrowException(bool write) const
throw and show appropriate message box
sqlite3_stmt * Prepare(enum StatementID id, const char *sql)
sqlite3 * DB()
static result_type Call(Arguments &&...arguments)
Null check of the installed function is done for you.
typename GlobalVariable< Factory, const std::function< SampleBlockFactoryPtr(AudacityProject &) >, nullptr, Options... >::Scope Scope
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
static int64_t GetDiskUsage(DBConnection &conn, SampleBlockID blockid)
abstract base class with methods to produce SampleBlock objects
Definition: SampleBlock.h:117
std::unordered_set< SampleBlockID > SampleBlockIDs
Definition: SampleBlock.h:144
Abstract class allows access to contents of a block of sound samples, serialization as XML,...
Definition: SampleBlock.h:46
virtual SampleBlockID GetBlockID() const =0
samplePtr ptr() const
Definition: SampleFormat.h:165
A MessageBoxException that shows a given, unvarying string.
Implementation of SampleBlockFactory using Sqlite database.
SampleBlockIDs GetActiveBlockIDs() override
SampleBlockPtr DoCreateFromXML(sampleFormat srcformat, const AttributesList &attrs) override
std::optional< SampleBlock::DeletionCallback::Scope > mScope
std::map< SampleBlockID, std::weak_ptr< SqliteSampleBlock > > AllBlocksMap
SqliteSampleBlockFactory(AudacityProject &project)
SampleBlockPtr DoCreate(constSamplePtr src, size_t numsamples, sampleFormat srcformat) override
void OnBeginPurge(size_t begin, size_t end)
const std::shared_ptr< ConnectionPtr > mppConnection
~SqliteSampleBlockFactory() override
SampleBlockPtr DoCreateSilent(size_t numsamples, sampleFormat srcformat) override
Observer::Subscription mUndoSubscription
Implementation of SampleBlock using Sqlite database.
size_t GetSpaceUsage() const override
size_t DoGetSamples(samplePtr dest, sampleFormat destformat, size_t sampleoffset, size_t numsamples) override
Sizes SetSizes(size_t numsamples, sampleFormat srcformat)
BlockSampleView GetFloatSampleView(bool mayThrow) override
bool GetSummary(float *dest, size_t frameoffset, size_t numframes, DBConnection::StatementID id, const char *sql)
void Load(SampleBlockID sbid)
std::pair< size_t, size_t > Sizes
Numbers of bytes needed for 256 and for 64k summaries.
void CalcSummary(Sizes sizes)
bool GetSummary64k(float *dest, size_t frameoffset, size_t numframes) override
Non-throwing, should fill with zeroes on failure.
SqliteSampleBlock(const std::shared_ptr< SqliteSampleBlockFactory > &pFactory)
std::weak_ptr< std::vector< float > > mCache
sampleFormat mSampleFormat
void CloseLock() noexcept override
size_t GetBlob(void *dest, sampleFormat destformat, sqlite3_stmt *stmt, sampleFormat srcformat, size_t srcoffset, size_t srcbytes)
void Commit(Sizes sizes)
ArrayOf< char > mSummary256
ArrayOf< char > mSummary64k
const std::shared_ptr< SqliteSampleBlockFactory > mpFactory
ArrayOf< char > mSamples
sqlite3 * DB() const
MinMaxRMS DoGetMinMaxRMS() const override
Gets extreme values for the entire block.
void SetSamples(constSamplePtr src, size_t numsamples, sampleFormat srcformat)
DBConnection * Conn() const
This must never be called for silent blocks.
double GetSumRms() const
SampleBlockID GetBlockID() const override
double GetSumMax() const
double GetSumMin() const
sampleFormat GetSampleFormat() const
bool GetSummary256(float *dest, size_t frameoffset, size_t numframes) override
Non-throwing, should fill with zeroes on failure.
size_t GetSampleCount() const override
void SaveXML(XMLWriter &xmlFile) override
static TrackList * FindUndoTracks(const UndoStackElem &state)
Definition: Track.cpp:1406
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:25
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:36
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:196
std::unique_ptr< ProgressDialog > MakeProgress(const TranslatableString &title, const TranslatableString &message, unsigned flags=(ProgressShowStop|ProgressShowCancel), const TranslatableString &remainingLabelText={})
Create and display a progress dialog.
Definition: BasicUI.h:292
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
__finl float_x4 __vecc sqrt(const float_x4 &a)
STL namespace.
Type of message published by UndoManager.
Definition: UndoManager.h:55
enum UndoRedoMessage::Type type
Holds one item with description and time range for the UndoManager.
Definition: UndoManager.h:117