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 {
176 }
177
178private:
179 void OnBeginPurge(size_t begin, size_t end);
180 void OnEndPurge();
181
183
186 std::function<void()> mSampleBlockDeletionCallback;
187 const std::shared_ptr<ConnectionPtr> mppConnection;
188
189 // Track all blocks that this factory has created, but don't control
190 // their lifetimes (so use weak_ptr)
191 // (Must also use weak pointers because the blocks have shared pointers
192 // to the factory and we can't have a leaky cycle of shared pointers)
194 std::map< SampleBlockID, std::weak_ptr< SqliteSampleBlock > >;
196};
197
199 : mProject{ project }
200 , mppConnection{ ConnectionPtr::Get(project).shared_from_this() }
201{
203 .Subscribe([this](UndoRedoMessage message){
204 switch (message.type) {
205 case UndoRedoMessage::BeginPurge:
206 return OnBeginPurge(message.begin, message.end);
207 case UndoRedoMessage::EndPurge:
208 return OnEndPurge();
209 default:
210 return;
211 }
212 });
213}
214
216
218 constSamplePtr src, size_t numsamples, sampleFormat srcformat )
219{
220 auto sb = std::make_shared<SqliteSampleBlock>(shared_from_this());
221 sb->SetSamples(src, numsamples, srcformat);
222 // block id has now been assigned
223 mAllBlocks[ sb->GetBlockID() ] = sb;
224 return sb;
225}
226
228{
229 SampleBlockIDs result;
230 for (auto end = mAllBlocks.end(), it = mAllBlocks.begin(); it != end;) {
231 if (it->second.expired())
232 // Tighten up the map
233 it = mAllBlocks.erase(it);
234 else {
235 result.insert( it->first );
236 ++it;
237 }
238 }
239 return result;
240}
241
243 size_t numsamples, sampleFormat )
244{
245 auto id = -static_cast< SampleBlockID >(numsamples);
246 auto &result = sSilentBlocks[ id ];
247 if ( !result ) {
248 result = std::make_shared<SqliteSampleBlock>(nullptr);
249 result->mBlockID = id;
250
251 // Ignore the supplied sample format
252 result->SetSizes(numsamples, floatSample);
253 result->mValid = true;
254 }
255
256 return result;
257}
258
259
261 sampleFormat srcformat, const AttributesList &attrs )
262{
263 // loop through attrs, which is a null-terminated list of attribute-value pairs
264 for (auto pair : attrs)
265 {
266 auto attr = pair.first;
267 auto value = pair.second;
268
269 long long nValue;
270
271 if (attr == "blockid" && value.TryGet(nValue))
272 return DoCreateFromId(srcformat, nValue);
273 }
274
275 return nullptr;
276}
277
279 sampleFormat srcformat, SampleBlockID id)
280{
281 if (id <= 0)
282 return DoCreateSilent(-id, floatSample);
283
284 // First see if this block id was previously loaded
285 auto& wb = mAllBlocks[id];
286
287 if (auto block = wb.lock())
288 return block;
289
290 // First sight of this id
291 auto ssb = std::make_shared<SqliteSampleBlock>(shared_from_this());
292 wb = ssb;
293 ssb->mSampleFormat = srcformat;
294 // This may throw database errors
295 // It initializes the rest of the fields
296 ssb->Load(static_cast<SampleBlockID>(id));
297
298 return ssb;
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{
349 if (mpFactory) {
350 mpFactory->OnSampleBlockDtor(*this);
351 }
352
353 if (IsSilent()) {
354 // The block object was constructed but failed to Load() or Commit().
355 // Or it's a silent block with no row in the database.
356 // Just let the stack unwind. Don't violate the assertion in
357 // Delete(), which may do odd recursive things in debug builds when it
358 // yields to the UI to put up a dialog, but then dispatches timer
359 // events that try again to finish recording.
360 return;
361 }
362
363 // See ProjectFileIO::Bypass() for a description of mIO.mBypass
364 GuardedCall( [this]{
365 if (!mLocked && !Conn()->ShouldBypass())
366 {
367 // In case Delete throws, don't let an exception escape a destructor,
368 // but we can still enqueue the delayed handler so that an error message
369 // is presented to the user.
370 // The failure in this case may be a less harmful waste of space in the
371 // database, which should not cause aborting of the attempted edit.
372 Delete();
373 }
374 } );
375}
376
378{
379 if (!mpFactory)
380 return nullptr;
381
382 auto &pConnection = mpFactory->mppConnection->mpConnection;
383 if (!pConnection) {
385 {
387 XO("Connection to project file is null"),
388 XO("Warning"),
389 "Error:_Disk_full_or_not_writable"
390 };
391 }
392 return pConnection.get();
393}
394
396{
397 mLocked = true;
398}
399
401{
402 return mBlockID;
403}
404
406{
407 return mSampleFormat;
408}
409
411{
412 return mSampleCount;
413}
414
416 sampleFormat destformat,
417 size_t sampleoffset,
418 size_t numsamples)
419{
420 if (IsSilent()) {
421 auto size = SAMPLE_SIZE(destformat);
422 memset(dest, 0, numsamples * size);
423 return numsamples;
424 }
425
426 // Prepare and cache statement...automatically finalized at DB close
427 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::GetSamples,
428 "SELECT samples FROM sampleblocks WHERE blockid = ?1;");
429
430 return GetBlob(dest,
431 destformat,
432 stmt,
434 sampleoffset * SAMPLE_SIZE(mSampleFormat),
436}
437
439 size_t numsamples,
440 sampleFormat srcformat)
441{
442 auto sizes = SetSizes(numsamples, srcformat);
444 memcpy(mSamples.get(), src, mSampleBytes);
445
446 CalcSummary( sizes );
447
448 Commit( sizes );
449}
450
452 size_t frameoffset,
453 size_t numframes)
454{
455 return GetSummary(dest, frameoffset, numframes, DBConnection::GetSummary256,
456 "SELECT summary256 FROM sampleblocks WHERE blockid = ?1;");
457}
458
460 size_t frameoffset,
461 size_t numframes)
462{
463 return GetSummary(dest, frameoffset, numframes, DBConnection::GetSummary64k,
464 "SELECT summary64k FROM sampleblocks WHERE blockid = ?1;");
465}
466
468 size_t frameoffset,
469 size_t numframes,
471 const char *sql)
472{
473 // Non-throwing, it returns true for success
474 bool silent = IsSilent();
475 if (!silent) {
476 // Not a silent block
477 try {
478 // Prepare and cache statement...automatically finalized at DB close
479 auto stmt = Conn()->Prepare(id, sql);
480 // Note GetBlob returns a size_t, not a bool
481 // REVIEW: An error in GetBlob() will throw an exception.
482 GetBlob(dest,
484 stmt,
486 frameoffset * fields * SAMPLE_SIZE(floatSample),
487 numframes * fields * SAMPLE_SIZE(floatSample));
488 return true;
489 }
490 catch ( const AudacityException & ) {
491 }
492 }
493 memset(dest, 0, 3 * numframes * sizeof( float ));
494 // Return true for success only if we didn't catch
495 return silent;
496}
497
499{
500 return mSumMin;
501}
502
504{
505 return mSumMax;
506}
507
509{
510 return mSumRms;
511}
512
519{
520 if (IsSilent())
521 return {};
522
523 float min = FLT_MAX;
524 float max = -FLT_MAX;
525 float sumsq = 0;
526
527 if (!mValid)
528 {
529 Load(mBlockID);
530 }
531
532 if (start < mSampleCount)
533 {
534 len = std::min(len, mSampleCount - start);
535
536 // TODO: actually use summaries
537 SampleBuffer blockData(len, floatSample);
538 float *samples = (float *) blockData.ptr();
539
540 size_t copied = DoGetSamples((samplePtr) samples, floatSample, start, len);
541 for (size_t i = 0; i < copied; ++i, ++samples)
542 {
543 float sample = *samples;
544
545 if (sample > max)
546 {
547 max = sample;
548 }
549
550 if (sample < min)
551 {
552 min = sample;
553 }
554
555 sumsq += (sample * sample);
556 }
557 }
558
559 return { min, max, (float) sqrt(sumsq / len) };
560}
561
566{
567 return { (float) mSumMin, (float) mSumMax, (float) mSumRms };
568}
569
571{
572 if (IsSilent())
573 return 0;
574 else
576}
577
579 sampleFormat destformat,
580 sqlite3_stmt *stmt,
581 sampleFormat srcformat,
582 size_t srcoffset,
583 size_t srcbytes)
584{
585 auto db = DB();
586
587 wxASSERT(!IsSilent());
588
589 if (!mValid)
590 {
591 Load(mBlockID);
592 }
593
594 int rc;
595 size_t minbytes = 0;
596
597 // Bind statement parameters
598 // Might return SQLITE_MISUSE which means it's our mistake that we violated
599 // preconditions; should return SQL_OK which is 0
600 if (sqlite3_bind_int64(stmt, 1, mBlockID))
601 {
603 "sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
604 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::GetBlob::bind");
605
606 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
607 }
608
609 // Execute the statement
610 rc = sqlite3_step(stmt);
611 if (rc != SQLITE_ROW)
612 {
613 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
614 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::GetBlob::step");
615
616 wxLogDebug(wxT("SqliteSampleBlock::GetBlob - SQLITE error %s"), sqlite3_errmsg(db));
617
618 // Clear statement bindings and rewind statement
619 sqlite3_clear_bindings(stmt);
620 sqlite3_reset(stmt);
621
622 // Just showing the user a simple message, not the library error too
623 // which isn't internationalized
624 // Actually this can lead to 'Could not read from file' error message
625 // but it can also lead to no error message at all and a flat line,
626 // depending on where GetBlob is called from.
627 // The latter can happen when repainting the screen.
628 // That possibly happens on a very slow machine. Possibly that's the
629 // right trade off when a machine can't keep up?
630 // ANSWER-ME: Do we always report an error when we should here?
631 Conn()->ThrowException( false );
632 }
633
634 // Retrieve returned data
635 samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0);
636 size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0);
637
638 srcoffset = std::min(srcoffset, blobbytes);
639 minbytes = std::min(srcbytes, blobbytes - srcoffset);
640
641 if (srcoffset != 0)
642 {
643 srcoffset += 0;
644 }
645
646 /*
647 Will dithering happen in CopySamples? Answering this as of 3.0.3 by
648 examining all uses.
649
650 As this function is called from GetSummary, no, because destination format
651 is float.
652
653 There is only one other call to this function, in DoGetSamples. At one
654 call to that function, in DoGetMinMaxRMS, again format is float always.
655
656 There is only one other call to DoGetSamples, in SampleBlock::GetSamples().
657 In one call to that function, in WaveformView.cpp, again format is float.
658
659 That leaves two calls in Sequence.cpp. One of those can be proved to be
660 used only in copy and paste operations, always supplying the same sample
661 format as the samples were stored in, therefore no dither.
662
663 That leaves uses of Sequence::Read(). There are uses of Read() in internal
664 operations also easily shown to use only the saved format, and
665 GetWaveDisplay() always reads as float.
666
667 The remaining use of Sequence::Read() is in Sequence::Get(). That is used
668 by WaveClip::Resample(), always fetching float. It is also used in
669 WaveClip::GetSamples().
670
671 There is only one use of that function not always fetching float, in
672 WaveTrack::Get().
673
674 It can be shown that the only paths to WaveTrack::Get() not specifying
675 floatSample are in Benchmark, which is only a diagnostic test, and there
676 the sample format is the same as what the track was constructed with.
677
678 Therefore, no dithering even there!
679 */
680 wxASSERT(destformat == floatSample || destformat == srcformat);
681
682 CopySamples(src + srcoffset,
683 srcformat,
684 (samplePtr) dest,
685 destformat,
686 minbytes / SAMPLE_SIZE(srcformat));
687
688 dest = ((samplePtr) dest) + minbytes;
689
690 if (srcbytes - minbytes)
691 {
692 memset(dest, 0, srcbytes - minbytes);
693 }
694
695 // Clear statement bindings and rewind statement
696 sqlite3_clear_bindings(stmt);
697 sqlite3_reset(stmt);
698
699 return srcbytes;
700}
701
703{
704 auto db = DB();
705 int rc;
706
707 wxASSERT(sbid > 0);
708
709 mValid = false;
710 mSampleCount = 0;
711 mSampleBytes = 0;
712 mSumMin = FLT_MAX;
713 mSumMax = -FLT_MAX;
714 mSumMin = 0.0;
715
716 // Prepare and cache statement...automatically finalized at DB close
717 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::LoadSampleBlock,
718 "SELECT sampleformat, summin, summax, sumrms,"
719 " length(samples)"
720 " FROM sampleblocks WHERE blockid = ?1;");
721
722 // Bind statement parameters
723 // Might return SQLITE_MISUSE which means it's our mistake that we violated
724 // preconditions; should return SQL_OK which is 0
725 if (sqlite3_bind_int64(stmt, 1, sbid))
726 {
727
728 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
729 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Load::bind");
730
731 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
732 }
733
734 // Execute the statement
735 rc = sqlite3_step(stmt);
736 if (rc != SQLITE_ROW)
737 {
738
739 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
740 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Load::step");
741
742
743 wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db));
744
745 // Clear statement bindings and rewind statement
746 sqlite3_clear_bindings(stmt);
747 sqlite3_reset(stmt);
748
749 // Just showing the user a simple message, not the library error too
750 // which isn't internationalized
751 Conn()->ThrowException( false );
752 }
753
754 // Retrieve returned data
755 mBlockID = sbid;
756 mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0);
757 mSumMin = sqlite3_column_double(stmt, 1);
758 mSumMax = sqlite3_column_double(stmt, 2);
759 mSumRms = sqlite3_column_double(stmt, 3);
760 mSampleBytes = sqlite3_column_int(stmt, 4);
762
763 // Clear statement bindings and rewind statement
764 sqlite3_clear_bindings(stmt);
765 sqlite3_reset(stmt);
766
767 mValid = true;
768}
769
771{
772 const auto mSummary256Bytes = sizes.first;
773 const auto mSummary64kBytes = sizes.second;
774
775 auto db = DB();
776 int rc;
777
778 // Prepare and cache statement...automatically finalized at DB close
779 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::InsertSampleBlock,
780 "INSERT INTO sampleblocks (sampleformat, summin, summax, sumrms,"
781 " summary256, summary64k, samples)"
782 " VALUES(?1,?2,?3,?4,?5,?6,?7);");
783
784 // Bind statement parameters
785 // Might return SQLITE_MISUSE which means it's our mistake that we violated
786 // preconditions; should return SQL_OK which is 0
787 if (sqlite3_bind_int(stmt, 1, static_cast<int>(mSampleFormat)) ||
788 sqlite3_bind_double(stmt, 2, mSumMin) ||
789 sqlite3_bind_double(stmt, 3, mSumMax) ||
790 sqlite3_bind_double(stmt, 4, mSumRms) ||
791 sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC) ||
792 sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC) ||
793 sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC))
794 {
795
797 "sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
798 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Commit::bind");
799
800
801 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
802 }
803
804 // Execute the statement
805 rc = sqlite3_step(stmt);
806 if (rc != SQLITE_DONE)
807 {
808 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
809 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Commit::step");
810
811 wxLogDebug(wxT("SqliteSampleBlock::Commit - SQLITE error %s"), sqlite3_errmsg(db));
812
813 // Clear statement bindings and rewind statement
814 sqlite3_clear_bindings(stmt);
815 sqlite3_reset(stmt);
816
817 // Just showing the user a simple message, not the library error too
818 // which isn't internationalized
819 Conn()->ThrowException( true );
820 }
821
822 // Retrieve returned data
823 mBlockID = sqlite3_last_insert_rowid(db);
824
825 // Reset local arrays
826 mSamples.reset();
827 mSummary256.reset();
828 mSummary64k.reset();
829 {
830 std::lock_guard<std::mutex> lock(mCacheMutex);
831 mCache.reset();
832 }
833
834 // Clear statement bindings and rewind statement
835 sqlite3_clear_bindings(stmt);
836 sqlite3_reset(stmt);
837
838 mValid = true;
839}
840
842{
843 auto db = DB();
844 int rc;
845
846 wxASSERT(!IsSilent());
847
848 // Prepare and cache statement...automatically finalized at DB close
849 sqlite3_stmt *stmt = Conn()->Prepare(DBConnection::DeleteSampleBlock,
850 "DELETE FROM sampleblocks WHERE blockid = ?1;");
851
852 // Bind statement parameters
853 // Might return SQLITE_MISUSE which means it's our mistake that we violated
854 // preconditions; should return SQL_OK which is 0
855 if (sqlite3_bind_int64(stmt, 1, mBlockID))
856 {
858 "sqlite3.rc", std::to_string(sqlite3_errcode(Conn()->DB())));
859 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Delete::bind");
860
861 wxASSERT_MSG(false, wxT("Binding failed...bug!!!"));
862 }
863
864 // Execute the statement
865 rc = sqlite3_step(stmt);
866 if (rc != SQLITE_DONE)
867 {
868 ADD_EXCEPTION_CONTEXT("sqlite3.rc", std::to_string(rc));
869 ADD_EXCEPTION_CONTEXT("sqlite3.context", "SqliteSampleBlock::Delete::step");
870
871 wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db));
872
873 // Clear statement bindings and rewind statement
874 sqlite3_clear_bindings(stmt);
875 sqlite3_reset(stmt);
876
877 // Just showing the user a simple message, not the library error too
878 // which isn't internationalized
879 Conn()->ThrowException( true );
880 }
881
882 // Clear statement bindings and rewind statement
883 sqlite3_clear_bindings(stmt);
884 sqlite3_reset(stmt);
885}
886
888{
889 xmlFile.WriteAttr(wxT("blockid"), mBlockID);
890}
891
893 size_t numsamples, sampleFormat srcformat ) -> Sizes
894{
895 mSampleFormat = srcformat;
896 mSampleCount = numsamples;
897 mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
898
899 int frames64k = (mSampleCount + 65535) / 65536;
900 int frames256 = frames64k * 256;
901 return { frames256 * bytesPerFrame, frames64k * bytesPerFrame };
902}
903
910{
911 const auto mSummary256Bytes = sizes.first;
912 const auto mSummary64kBytes = sizes.second;
913
914 Floats samplebuffer;
915 float *samples;
916
918 {
919 samples = (float *) mSamples.get();
920 }
921 else
922 {
923 samplebuffer.reinit((unsigned) mSampleCount);
925 samplebuffer.get(), mSampleCount);
926 samples = samplebuffer.get();
927 }
928
929 mSummary256.reinit(mSummary256Bytes);
930 mSummary64k.reinit(mSummary64kBytes);
931
932 float *summary256 = (float *) mSummary256.get();
933 float *summary64k = (float *) mSummary64k.get();
934
935 float min;
936 float max;
937 float sumsq;
938 double totalSquares = 0.0;
939 double fraction = 0.0;
940
941 // Recalc 256 summaries
942 int sumLen = (mSampleCount + 255) / 256;
943 int summaries = 256;
944
945 for (int i = 0; i < sumLen; ++i)
946 {
947 min = samples[i * 256];
948 max = samples[i * 256];
949 sumsq = min * min;
950
951 int jcount = 256;
952 if (jcount > mSampleCount - i * 256)
953 {
954 jcount = mSampleCount - i * 256;
955 fraction = 1.0 - (jcount / 256.0);
956 }
957
958 for (int j = 1; j < jcount; ++j)
959 {
960 float f1 = samples[i * 256 + j];
961 sumsq += f1 * f1;
962
963 if (f1 < min)
964 {
965 min = f1;
966 }
967 else if (f1 > max)
968 {
969 max = f1;
970 }
971 }
972
973 totalSquares += sumsq;
974
975 summary256[i * fields] = min;
976 summary256[i * fields + 1] = max;
977 // The rms is correct, but this may be for less than 256 samples in last loop.
978 summary256[i * fields + 2] = (float) sqrt(sumsq / jcount);
979 }
980
981 for (int i = sumLen, frames256 = mSummary256Bytes / bytesPerFrame;
982 i < frames256; ++i)
983 {
984 // filling in the remaining bits with non-harming/contributing values
985 // rms values are not "non-harming", so keep count of them:
986 summaries--;
987 summary256[i * fields] = FLT_MAX; // min
988 summary256[i * fields + 1] = -FLT_MAX; // max
989 summary256[i * fields + 2] = 0.0f; // rms
990 }
991
992 // Calculate now while we can do it accurately
993 mSumRms = sqrt(totalSquares / mSampleCount);
994
995 // Recalc 64K summaries
996 sumLen = (mSampleCount + 65535) / 65536;
997
998 for (int i = 0; i < sumLen; ++i)
999 {
1000 min = summary256[3 * i * 256];
1001 max = summary256[3 * i * 256 + 1];
1002 sumsq = summary256[3 * i * 256 + 2];
1003 sumsq *= sumsq;
1004
1005 for (int j = 1; j < 256; ++j)
1006 {
1007 // we can overflow the useful summary256 values here, but have put
1008 // non-harmful values in them
1009 if (summary256[3 * (i * 256 + j)] < min)
1010 {
1011 min = summary256[3 * (i * 256 + j)];
1012 }
1013
1014 if (summary256[3 * (i * 256 + j) + 1] > max)
1015 {
1016 max = summary256[3 * (i * 256 + j) + 1];
1017 }
1018
1019 float r1 = summary256[3 * (i * 256 + j) + 2];
1020 sumsq += r1 * r1;
1021 }
1022
1023 double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction;
1024 float rms = (float) sqrt(sumsq / denom);
1025
1026 summary64k[i * fields] = min;
1027 summary64k[i * fields + 1] = max;
1028 summary64k[i * fields + 2] = rms;
1029 }
1030
1031 for (int i = sumLen, frames64k = mSummary64kBytes / bytesPerFrame;
1032 i < frames64k; ++i)
1033 {
1034 wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here?
1035
1036 summary64k[i * fields] = 0.0f; // probably should be FLT_MAX, need a test case
1037 summary64k[i * fields + 1] = 0.0f; // probably should be -FLT_MAX, need a test case
1038 summary64k[i * fields + 2] = 0.0f; // just padding
1039 }
1040
1041 // Recalc block-level summary (mRMS already calculated)
1042 min = summary64k[0];
1043 max = summary64k[1];
1044
1045 for (int i = 1; i < sumLen; ++i)
1046 {
1047 if (summary64k[i * fields] < min)
1048 {
1049 min = summary64k[i * fields];
1050 }
1051
1052 if (summary64k[i * fields + 1] > max)
1053 {
1054 max = summary64k[i * fields + 1];
1055 }
1056 }
1057
1058 mSumMin = min;
1059 mSumMax = max;
1060}
1061
1063
1065 AudacityProject &project, size_t begin, size_t end)
1066{
1067 using namespace WaveTrackUtilities;
1069
1070 // Collect ids that survive
1071 using namespace WaveTrackUtilities;
1072 SampleBlockIDSet wontDelete;
1073 auto f = [&](const UndoStackElem &elem) {
1074 if (auto pTracks = UndoTracks::Find(elem))
1075 InspectBlocks(*pTracks, {}, &wontDelete);
1076 };
1077 manager.VisitStates(f, 0, begin);
1078 manager.VisitStates(f, end, manager.GetNumStates());
1079 if (const auto saved = manager.GetSavedState(); saved >= 0)
1080 manager.VisitStates(f, saved, saved + 1);
1081 InspectBlocks(TrackList::Get(project), {}, &wontDelete);
1082
1083 // Collect ids that won't survive (and are not negative pseudo ids)
1084 SampleBlockIDSet seen, mayDelete;
1085 manager.VisitStates([&](const UndoStackElem &elem) {
1086 if (auto pTracks = UndoTracks::Find(elem)) {
1087 InspectBlocks(*pTracks,
1088 [&](SampleBlockConstPtr pBlock){
1089 auto id = pBlock->GetBlockID();
1090 if (id > 0 && !wontDelete.count(id))
1091 mayDelete.insert(id);
1092 },
1093 &seen
1094 );
1095 }
1096 }, begin, end);
1097 return mayDelete.size();
1098}
1099
1101{
1102 // Install a callback function that updates a progress indicator
1103 using namespace BasicUI;
1104
1105 //Avoid showing dialog to the user if purge operation
1106 //does not take much time, as it will resign focus from main window
1107 //but dialog itself may not be presented to the user at all.
1108 //On MacOS 13 (bug #3975) focus isn't restored in that case.
1109 constexpr auto ProgressDialogShowDelay = std::chrono::milliseconds (200);
1110 const auto nToDelete = EstimateRemovedBlocks(mProject, begin, end);
1111 if(nToDelete == 0)
1112 return;
1113 auto purgeStartTime = std::chrono::system_clock::now();
1114 std::shared_ptr<ProgressDialog> progressDialog;
1115 mSampleBlockDeletionCallback = [=, nDeleted = 0] (void) mutable {
1116 ++nDeleted;
1117 if(!progressDialog)
1118 {
1119 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
1120 std::chrono::system_clock::now() - purgeStartTime);
1121 if(elapsed >= ProgressDialogShowDelay)
1122 progressDialog = MakeProgress(XO("Progress"), XO("Discarding undo/redo history"), 0);
1123 }
1124 else
1125 progressDialog->Poll(nDeleted, nToDelete);
1126 };
1127}
1128
1130{
1132}
1133
1134// Inject our database implementation at startup
1136{
1137 return std::make_shared<SqliteSampleBlockFactory>( project );
1138} };
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:115
std::unordered_set< SampleBlockID > SampleBlockIDs
Definition: SampleBlock.h:145
Abstract class allows access to contents of a block of sound samples, serialization as XML,...
Definition: SampleBlock.h:47
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
void OnSampleBlockDtor(const SampleBlock &)
std::map< SampleBlockID, std::weak_ptr< SqliteSampleBlock > > AllBlocksMap
SqliteSampleBlockFactory(AudacityProject &project)
SampleBlockPtr DoCreate(constSamplePtr src, size_t numsamples, sampleFormat srcformat) override
std::function< void()> mSampleBlockDeletionCallback
SampleBlockPtr DoCreateFromId(sampleFormat srcformat, SampleBlockID id) 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 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:202
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:302
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