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