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