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