mirror of
https://github.com/cookiengineer/audacity
synced 2026-03-06 14:35:32 +01:00
Unitary changes (#599)
* Define SampleBlockFactory replacing static members of SampleBlock... ... This will become an abstract base class * Sequence and WaveTrack only store SampleBlockFactory not Project... ... This adds a dependency from Track to SampleBlock which temporarily enlarges a cycle in the dependency graph * Register a global factory of SampleBlockFactory... ... so that later we can make an abstract SampleBlockFactory, separate from the concrete implementation in terms of sqlite, and inject the dependency at startup avoiding static dependency * New concrete classes SqliteSampleBlock, SqliteSampleBlockFactory... ... separated from abstract base classes and put into a new source file, breaking dependency cycles, and perhaps allowing easy reimplementation for other databases in the future. Note that the new file is a header-less plug-in! Nothing depends on it. It uses static initialization to influence the program's behavior. * Compile dependency on sqlite3.h limited to just two .cpp files... ... these are ProjectFileIO.cpp and SqliteSampleBlock.cpp. But there is still close cooperation of ProjectFileIO and SqliteSampleBlock.cpp. This suggests that these files ought to be merged, and perhaps ProjectFileIO also needs to be split into abstract and concrete classes, and there should be another injection of a factory function at startup. That will make the choice of database implementation even more modular. Also removed one unnecessary inclusion of ProjectFileIO.h * Fix crashes cutting and pasting cross-project... ... in case the source project is closed before the paste happens. This caused destruction of the ProjectFileIO object and a closing of the sqlite database with the sample data in it, leaving dangling references in the SqliteSampleBlock objects. The fix is that the SqliteSampleBlockFactory object holds a shared_ptr to the ProjectFileIO object. So the clipboard may own WaveTracks, which own WaveClips, which own Sequences, which own SqliteSampleBlockFactories, which keep the ProjectFileIO and the database connection alive until the clipboard is cleared. The consequence of the fix is delayed closing of the entire database associated with the source project. If the source project is reopened before the clipboard is cleared, will there be correct concurrent access to the same persistent store? My preliminary trials suggest this is so (reopening a saved project, deleting from it, closing it again -- the clipboard contents are still unchanged and available).
This commit is contained in:
881
src/SqliteSampleBlock.cpp
Normal file
881
src/SqliteSampleBlock.cpp
Normal file
@@ -0,0 +1,881 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
SqliteSampleBlock.cpp
|
||||
|
||||
Paul Licameli -- split from SampleBlock.cpp and SampleBlock.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include <float.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "SampleFormat.h"
|
||||
#include "ProjectFileIO.h"
|
||||
#include "xml/XMLTagHandler.h"
|
||||
|
||||
#include "SampleBlock.h" // to inherit
|
||||
|
||||
///\brief Implementation of @ref SampleBlock using Sqlite database
|
||||
class SqliteSampleBlock final : public SampleBlock
|
||||
{
|
||||
public:
|
||||
|
||||
explicit SqliteSampleBlock(AudacityProject *project);
|
||||
~SqliteSampleBlock() override;
|
||||
|
||||
void Lock() override;
|
||||
void Unlock() override;
|
||||
void CloseLock() override;
|
||||
|
||||
bool SetSamples(samplePtr src, size_t numsamples, sampleFormat srcformat);
|
||||
|
||||
bool SetSilent(size_t numsamples, sampleFormat srcformat);
|
||||
|
||||
bool Commit();
|
||||
|
||||
void Delete();
|
||||
|
||||
SampleBlockID GetBlockID() override;
|
||||
|
||||
size_t GetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples) override;
|
||||
sampleFormat GetSampleFormat() const;
|
||||
size_t GetSampleCount() const override;
|
||||
|
||||
bool GetSummary256(float *dest, size_t frameoffset, size_t numframes) override;
|
||||
bool GetSummary64k(float *dest, size_t frameoffset, size_t numframes) override;
|
||||
double GetSumMin() const;
|
||||
double GetSumMax() const;
|
||||
double GetSumRms() const;
|
||||
|
||||
/// Gets extreme values for the specified region
|
||||
MinMaxRMS GetMinMaxRMS(size_t start, size_t len) override;
|
||||
|
||||
/// Gets extreme values for the entire block
|
||||
MinMaxRMS GetMinMaxRMS() const override;
|
||||
|
||||
size_t GetSpaceUsage() const override;
|
||||
void SaveXML(XMLWriter &xmlFile) override;
|
||||
|
||||
private:
|
||||
bool Load(SampleBlockID sbid);
|
||||
bool GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
const char *srccolumn,
|
||||
size_t srcbytes);
|
||||
size_t GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
const char *srccolumn,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes);
|
||||
void CalcSummary();
|
||||
|
||||
private:
|
||||
friend SqliteSampleBlockFactory;
|
||||
|
||||
ProjectFileIO & mIO;
|
||||
bool mValid;
|
||||
bool mDirty;
|
||||
bool mSilent;
|
||||
int mRefCnt;
|
||||
|
||||
SampleBlockID mBlockID;
|
||||
|
||||
ArrayOf<char> mSamples;
|
||||
size_t mSampleBytes;
|
||||
size_t mSampleCount;
|
||||
sampleFormat mSampleFormat;
|
||||
|
||||
ArrayOf<char> mSummary256;
|
||||
size_t mSummary256Bytes;
|
||||
ArrayOf<char> mSummary64k;
|
||||
size_t mSummary64kBytes;
|
||||
double mSumMin;
|
||||
double mSumMax;
|
||||
double mSumRms;
|
||||
|
||||
const char *columns =
|
||||
"sampleformat, summin, summax, sumrms, summary256, summary64k, samples";
|
||||
|
||||
#if defined(WORDS_BIGENDIAN)
|
||||
#error All sample block data is little endian...big endian not yet supported
|
||||
#endif
|
||||
};
|
||||
|
||||
///\brief Implementation of @ref SampleBlockFactory using Sqlite database
|
||||
class SqliteSampleBlockFactory final : public SampleBlockFactory
|
||||
{
|
||||
public:
|
||||
explicit SqliteSampleBlockFactory( AudacityProject &project );
|
||||
|
||||
~SqliteSampleBlockFactory() override;
|
||||
|
||||
SampleBlockPtr Get(SampleBlockID sbid) override;
|
||||
|
||||
SampleBlockPtr Create(samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat) override;
|
||||
|
||||
SampleBlockPtr CreateSilent(
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat) override;
|
||||
|
||||
SampleBlockPtr CreateFromXML(
|
||||
sampleFormat srcformat,
|
||||
const wxChar **attrs) override;
|
||||
|
||||
private:
|
||||
AudacityProject &mProject;
|
||||
std::shared_ptr<ProjectFileIO> mpIO;
|
||||
};
|
||||
|
||||
SqliteSampleBlockFactory::SqliteSampleBlockFactory( AudacityProject &project )
|
||||
: mProject{ project }
|
||||
, mpIO{ ProjectFileIO::Get(project).shared_from_this() }
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SqliteSampleBlockFactory::~SqliteSampleBlockFactory() = default;
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::Create(
|
||||
samplePtr src, size_t numsamples, sampleFormat srcformat )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (sb->SetSamples(src, numsamples, srcformat))
|
||||
{
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::CreateSilent(
|
||||
size_t numsamples, sampleFormat srcformat )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (sb->SetSilent(numsamples, srcformat))
|
||||
{
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::CreateFromXML(
|
||||
sampleFormat srcformat, const wxChar **attrs )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
sb->mSampleFormat = srcformat;
|
||||
|
||||
int found = 0;
|
||||
|
||||
// loop through attrs, which is a null-terminated list of attribute-value pairs
|
||||
while(*attrs)
|
||||
{
|
||||
const wxChar *attr = *attrs++;
|
||||
const wxChar *value = *attrs++;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const wxString strValue = value; // promote string, we need this for all
|
||||
double dblValue;
|
||||
long long nValue;
|
||||
|
||||
if (XMLValueChecker::IsGoodInt(strValue) && strValue.ToLongLong(&nValue) && (nValue >= 0))
|
||||
{
|
||||
if (wxStrcmp(attr, wxT("blockid")) == 0)
|
||||
{
|
||||
if (!sb->Load((SampleBlockID) nValue))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
found++;
|
||||
}
|
||||
else if (wxStrcmp(attr, wxT("samplecount")) == 0)
|
||||
{
|
||||
sb->mSampleCount = nValue;
|
||||
sb->mSampleBytes = sb->mSampleCount * SAMPLE_SIZE(sb->mSampleFormat);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
else if (XMLValueChecker::IsGoodString(strValue) && Internat::CompatibleToDouble(strValue, &dblValue))
|
||||
{
|
||||
if (wxStricmp(attr, wxT("min")) == 0)
|
||||
{
|
||||
sb->mSumMin = dblValue;
|
||||
found++;
|
||||
}
|
||||
else if (wxStricmp(attr, wxT("max")) == 0)
|
||||
{
|
||||
sb->mSumMax = dblValue;
|
||||
found++;
|
||||
}
|
||||
else if ((wxStricmp(attr, wxT("rms")) == 0) && (dblValue >= 0.0))
|
||||
{
|
||||
sb->mSumRms = dblValue;
|
||||
found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Were all attributes found?
|
||||
if (found != 5)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
SampleBlockPtr SqliteSampleBlockFactory::Get( SampleBlockID sbid )
|
||||
{
|
||||
auto sb = std::make_shared<SqliteSampleBlock>(&mProject);
|
||||
|
||||
if (sb)
|
||||
{
|
||||
if (!sb->Load(sbid))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
SqliteSampleBlock::SqliteSampleBlock(AudacityProject *project)
|
||||
: mIO(ProjectFileIO::Get(*project))
|
||||
{
|
||||
mValid = false;
|
||||
mSilent = false;
|
||||
mRefCnt = 0;
|
||||
|
||||
mBlockID = 0;
|
||||
|
||||
mSampleFormat = floatSample;
|
||||
mSampleBytes = 0;
|
||||
mSampleCount = 0;
|
||||
|
||||
mSummary256Bytes = 0;
|
||||
mSummary64kBytes = 0;
|
||||
mSumMin = 0.0;
|
||||
mSumMax = 0.0;
|
||||
mSumRms = 0.0;
|
||||
}
|
||||
|
||||
SqliteSampleBlock::~SqliteSampleBlock()
|
||||
{
|
||||
if (mRefCnt == 0)
|
||||
{
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::Lock()
|
||||
{
|
||||
++mRefCnt;
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::Unlock()
|
||||
{
|
||||
--mRefCnt;
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::CloseLock()
|
||||
{
|
||||
Lock();
|
||||
}
|
||||
|
||||
SampleBlockID SqliteSampleBlock::GetBlockID()
|
||||
{
|
||||
return mBlockID;
|
||||
}
|
||||
|
||||
sampleFormat SqliteSampleBlock::GetSampleFormat() const
|
||||
{
|
||||
return mSampleFormat;
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetSampleCount() const
|
||||
{
|
||||
return mSampleCount;
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
size_t sampleoffset,
|
||||
size_t numsamples)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
destformat,
|
||||
"samples",
|
||||
mSampleFormat,
|
||||
sampleoffset * SAMPLE_SIZE(mSampleFormat),
|
||||
numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::SetSamples(samplePtr src,
|
||||
size_t numsamples,
|
||||
sampleFormat srcformat)
|
||||
{
|
||||
mSampleFormat = srcformat;
|
||||
|
||||
mSampleCount = numsamples;
|
||||
mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
mSamples.reinit(mSampleBytes);
|
||||
memcpy(mSamples.get(), src, mSampleBytes);
|
||||
|
||||
CalcSummary();
|
||||
|
||||
return Commit();
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::SetSilent(size_t numsamples, sampleFormat srcformat)
|
||||
{
|
||||
mSampleFormat = srcformat;
|
||||
|
||||
mSampleCount = numsamples;
|
||||
mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
mSamples.reinit(mSampleBytes);
|
||||
memset(mSamples.get(), 0, mSampleBytes);
|
||||
|
||||
CalcSummary();
|
||||
|
||||
mSilent = true;
|
||||
|
||||
return Commit();
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary256(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest, frameoffset, numframes, "summary256", mSummary256Bytes);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary64k(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes)
|
||||
{
|
||||
return GetSummary(dest, frameoffset, numframes, "summary64k", mSummary64kBytes);
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::GetSummary(float *dest,
|
||||
size_t frameoffset,
|
||||
size_t numframes,
|
||||
const char *srccolumn,
|
||||
size_t srcbytes)
|
||||
{
|
||||
return GetBlob(dest,
|
||||
floatSample,
|
||||
srccolumn,
|
||||
floatSample,
|
||||
frameoffset * 3 * SAMPLE_SIZE(floatSample),
|
||||
numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample);
|
||||
}
|
||||
|
||||
double SqliteSampleBlock::GetSumMin() const
|
||||
{
|
||||
return mSumMin;
|
||||
}
|
||||
|
||||
double SqliteSampleBlock::GetSumMax() const
|
||||
{
|
||||
return mSumMax;
|
||||
}
|
||||
|
||||
double SqliteSampleBlock::GetSumRms() const
|
||||
{
|
||||
return mSumRms;
|
||||
}
|
||||
|
||||
/// Retrieves the minimum, maximum, and maximum RMS of the
|
||||
/// specified sample data in this block.
|
||||
///
|
||||
/// @param start The offset in this block where the region should begin
|
||||
/// @param len The number of samples to include in the region
|
||||
MinMaxRMS SqliteSampleBlock::GetMinMaxRMS(size_t start, size_t len)
|
||||
{
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
float sumsq = 0;
|
||||
|
||||
if (!mValid && mBlockID)
|
||||
{
|
||||
Load(mBlockID);
|
||||
}
|
||||
|
||||
if (start < mSampleCount)
|
||||
{
|
||||
len = std::min(len, mSampleCount - start);
|
||||
|
||||
// TODO: actually use summaries
|
||||
SampleBuffer blockData(len, floatSample);
|
||||
float *samples = (float *) blockData.ptr();
|
||||
|
||||
size_t copied = GetBlob(samples,
|
||||
floatSample,
|
||||
"samples",
|
||||
mSampleFormat,
|
||||
start * SAMPLE_SIZE(mSampleFormat),
|
||||
len * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
|
||||
for (size_t i = 0; i < copied; ++i, ++samples)
|
||||
{
|
||||
float sample = *samples;
|
||||
|
||||
if (sample > max)
|
||||
{
|
||||
max = sample;
|
||||
}
|
||||
|
||||
if (sample < min)
|
||||
{
|
||||
min = sample;
|
||||
}
|
||||
|
||||
sumsq += (sample * sample);
|
||||
}
|
||||
}
|
||||
|
||||
return { min, max, (float) sqrt(sumsq / len) };
|
||||
}
|
||||
|
||||
/// Retrieves the minimum, maximum, and maximum RMS of this entire
|
||||
/// block. This is faster than the other GetMinMax function since
|
||||
/// these values are already computed.
|
||||
MinMaxRMS SqliteSampleBlock::GetMinMaxRMS() const
|
||||
{
|
||||
return { (float) mSumMin, (float) mSumMax, (float) mSumRms };
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetSpaceUsage() const
|
||||
{
|
||||
return mSampleCount * SAMPLE_SIZE(mSampleFormat);
|
||||
}
|
||||
|
||||
size_t SqliteSampleBlock::GetBlob(void *dest,
|
||||
sampleFormat destformat,
|
||||
const char *srccolumn,
|
||||
sampleFormat srcformat,
|
||||
size_t srcoffset,
|
||||
size_t srcbytes)
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
wxASSERT(mBlockID > 0);
|
||||
|
||||
if (!mValid && mBlockID)
|
||||
{
|
||||
Load(mBlockID);
|
||||
}
|
||||
|
||||
int rc;
|
||||
size_t minbytes = 0;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"SELECT %s FROM sampleblocks WHERE blockid = %d;",
|
||||
srccolumn,
|
||||
mBlockID);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0);
|
||||
size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0);
|
||||
|
||||
srcoffset = std::min(srcoffset, blobbytes);
|
||||
minbytes = std::min(srcbytes, blobbytes - srcoffset);
|
||||
|
||||
if (srcoffset != 0)
|
||||
{
|
||||
srcoffset += 0;
|
||||
}
|
||||
CopySamples(src + srcoffset,
|
||||
srcformat,
|
||||
(samplePtr) dest,
|
||||
destformat,
|
||||
minbytes / SAMPLE_SIZE(srcformat));
|
||||
|
||||
dest = ((samplePtr) dest) + minbytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcbytes - minbytes)
|
||||
{
|
||||
memset(dest, 0, srcbytes - minbytes);
|
||||
}
|
||||
|
||||
return srcbytes;
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::Load(SampleBlockID sbid)
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
wxASSERT(sbid > 0);
|
||||
|
||||
int rc;
|
||||
|
||||
mValid = false;
|
||||
mSummary256Bytes = 0;
|
||||
mSummary64kBytes = 0;
|
||||
mSampleCount = 0;
|
||||
mSampleBytes = 0;
|
||||
mSumMin = FLT_MAX;
|
||||
mSumMax = -FLT_MAX;
|
||||
mSumMin = 0.0;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"SELECT sampleformat, summin, summax, sumrms,"
|
||||
" length('summary256'), length('summary64k'), length('samples')"
|
||||
" FROM sampleblocks WHERE blockid = %d;",
|
||||
sbid);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockID = sbid;
|
||||
mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0);
|
||||
mSumMin = sqlite3_column_double(stmt, 1);
|
||||
mSumMax = sqlite3_column_double(stmt, 2);
|
||||
mSumRms = sqlite3_column_double(stmt, 3);
|
||||
mSummary256Bytes = sqlite3_column_int(stmt, 4);
|
||||
mSummary64kBytes = sqlite3_column_int(stmt, 5);
|
||||
mSampleBytes = sqlite3_column_int(stmt, 6);
|
||||
mSampleCount = mSampleBytes / SAMPLE_SIZE(mSampleFormat);
|
||||
|
||||
mValid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqliteSampleBlock::Commit()
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
int rc;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"INSERT INTO sampleblocks (%s) VALUES(?,?,?,?,?,?,?);",
|
||||
columns);
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
auto cleanup = finally([&]
|
||||
{
|
||||
if (stmt)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
});
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
// BIND SQL sampleblocks
|
||||
sqlite3_bind_int(stmt, 1, mSampleFormat);
|
||||
sqlite3_bind_double(stmt, 2, mSumMin);
|
||||
sqlite3_bind_double(stmt, 3, mSumMax);
|
||||
sqlite3_bind_double(stmt, 4, mSumRms);
|
||||
sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockID = sqlite3_last_insert_rowid(db);
|
||||
|
||||
mSamples.reset();
|
||||
mSummary256.reset();
|
||||
mSummary64k.reset();
|
||||
|
||||
mValid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::Delete()
|
||||
{
|
||||
auto db = mIO.DB();
|
||||
|
||||
if (mBlockID)
|
||||
{
|
||||
int rc;
|
||||
|
||||
char sql[256];
|
||||
sqlite3_snprintf(sizeof(sql),
|
||||
sql,
|
||||
"DELETE FROM sampleblocks WHERE blockid = %lld;",
|
||||
mBlockID);
|
||||
|
||||
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db));
|
||||
// handle error
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SqliteSampleBlock::SaveXML(XMLWriter &xmlFile)
|
||||
{
|
||||
xmlFile.WriteAttr(wxT("blockid"), mBlockID);
|
||||
xmlFile.WriteAttr(wxT("samplecount"), mSampleCount);
|
||||
xmlFile.WriteAttr(wxT("len256"), mSummary256Bytes);
|
||||
xmlFile.WriteAttr(wxT("len64k"), mSummary64kBytes);
|
||||
xmlFile.WriteAttr(wxT("min"), mSumMin);
|
||||
xmlFile.WriteAttr(wxT("max"), mSumMax);
|
||||
xmlFile.WriteAttr(wxT("rms"), mSumRms);
|
||||
}
|
||||
|
||||
/// Calculates summary block data describing this sample data.
|
||||
///
|
||||
/// This method also has the side effect of setting the mSumMin,
|
||||
/// mSumMax, and mSumRms members of this class.
|
||||
///
|
||||
/// @param buffer A buffer containing the sample data to be analyzed
|
||||
/// @param len The length of the sample data
|
||||
/// @param format The format of the sample data.
|
||||
void SqliteSampleBlock::CalcSummary()
|
||||
{
|
||||
Floats samplebuffer;
|
||||
float *samples;
|
||||
|
||||
if (mSampleFormat == floatSample)
|
||||
{
|
||||
samples = (float *) mSamples.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
samplebuffer.reinit((unsigned) mSampleCount);
|
||||
CopySamples(mSamples.get(),
|
||||
mSampleFormat,
|
||||
(samplePtr) samplebuffer.get(),
|
||||
floatSample,
|
||||
mSampleCount);
|
||||
samples = samplebuffer.get();
|
||||
}
|
||||
|
||||
int fields = 3; /* min, max, rms */
|
||||
int bytesPerFrame = fields * sizeof(float);
|
||||
int frames64k = (mSampleCount + 65535) / 65536;
|
||||
int frames256 = frames64k * 256;
|
||||
|
||||
mSummary256Bytes = frames256 * bytesPerFrame;
|
||||
mSummary64kBytes = frames64k * bytesPerFrame;
|
||||
|
||||
mSummary256.reinit(mSummary256Bytes);
|
||||
mSummary64k.reinit(mSummary64kBytes);
|
||||
|
||||
float *summary256 = (float *) mSummary256.get();
|
||||
float *summary64k = (float *) mSummary64k.get();
|
||||
|
||||
float min;
|
||||
float max;
|
||||
float sumsq;
|
||||
double totalSquares = 0.0;
|
||||
double fraction = 0.0;
|
||||
|
||||
// Recalc 256 summaries
|
||||
int sumLen = (mSampleCount + 255) / 256;
|
||||
int summaries = 256;
|
||||
|
||||
for (int i = 0; i < sumLen; ++i)
|
||||
{
|
||||
min = samples[i * 256];
|
||||
max = samples[i * 256];
|
||||
sumsq = min * min;
|
||||
|
||||
int jcount = 256;
|
||||
if (jcount > mSampleCount - i * 256)
|
||||
{
|
||||
jcount = mSampleCount - i * 256;
|
||||
fraction = 1.0 - (jcount / 256.0);
|
||||
}
|
||||
|
||||
for (int j = 1; j < jcount; ++j)
|
||||
{
|
||||
float f1 = samples[i * 256 + j];
|
||||
sumsq += f1 * f1;
|
||||
|
||||
if (f1 < min)
|
||||
{
|
||||
min = f1;
|
||||
}
|
||||
else if (f1 > max)
|
||||
{
|
||||
max = f1;
|
||||
}
|
||||
}
|
||||
|
||||
totalSquares += sumsq;
|
||||
|
||||
summary256[i * 3] = min;
|
||||
summary256[i * 3 + 1] = max;
|
||||
// The rms is correct, but this may be for less than 256 samples in last loop.
|
||||
summary256[i * 3 + 2] = (float) sqrt(sumsq / jcount);
|
||||
}
|
||||
|
||||
for (int i = sumLen; i < frames256; ++i)
|
||||
{
|
||||
// filling in the remaining bits with non-harming/contributing values
|
||||
// rms values are not "non-harming", so keep count of them:
|
||||
summaries--;
|
||||
summary256[i * 3] = FLT_MAX; // min
|
||||
summary256[i * 3 + 1] = -FLT_MAX; // max
|
||||
summary256[i * 3 + 2] = 0.0f; // rms
|
||||
}
|
||||
|
||||
// Calculate now while we can do it accurately
|
||||
mSumRms = sqrt(totalSquares / mSampleCount);
|
||||
|
||||
// Recalc 64K summaries
|
||||
sumLen = (mSampleCount + 65535) / 65536;
|
||||
|
||||
for (int i = 0; i < sumLen; ++i)
|
||||
{
|
||||
min = summary256[3 * i * 256];
|
||||
max = summary256[3 * i * 256 + 1];
|
||||
sumsq = summary256[3 * i * 256 + 2];
|
||||
sumsq *= sumsq;
|
||||
|
||||
for (int j = 1; j < 256; ++j)
|
||||
{
|
||||
// we can overflow the useful summary256 values here, but have put
|
||||
// non-harmful values in them
|
||||
if (summary256[3 * (i * 256 + j)] < min)
|
||||
{
|
||||
min = summary256[3 * (i * 256 + j)];
|
||||
}
|
||||
|
||||
if (summary256[3 * (i * 256 + j) + 1] > max)
|
||||
{
|
||||
max = summary256[3 * (i * 256 + j) + 1];
|
||||
}
|
||||
|
||||
float r1 = summary256[3 * (i * 256 + j) + 2];
|
||||
sumsq += r1 * r1;
|
||||
}
|
||||
|
||||
double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction;
|
||||
float rms = (float) sqrt(sumsq / denom);
|
||||
|
||||
summary64k[i * 3] = min;
|
||||
summary64k[i * 3 + 1] = max;
|
||||
summary64k[i * 3 + 2] = rms;
|
||||
}
|
||||
|
||||
for (int i = sumLen; i < frames64k; ++i)
|
||||
{
|
||||
wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here?
|
||||
|
||||
summary64k[i * 3] = 0.0f; // probably should be FLT_MAX, need a test case
|
||||
summary64k[i * 3 + 1] = 0.0f; // probably should be -FLT_MAX, need a test case
|
||||
summary64k[i * 3 + 2] = 0.0f; // just padding
|
||||
}
|
||||
|
||||
// Recalc block-level summary (mRMS already calculated)
|
||||
min = summary64k[0];
|
||||
max = summary64k[1];
|
||||
|
||||
for (int i = 1; i < sumLen; ++i)
|
||||
{
|
||||
if (summary64k[i * 3] < min)
|
||||
{
|
||||
min = summary64k[i * 3];
|
||||
}
|
||||
|
||||
if (summary64k[i * 3 + 1] > max)
|
||||
{
|
||||
max = summary64k[i * 3 + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSumMin = min;
|
||||
mSumMax = max;
|
||||
}
|
||||
|
||||
// Inject our database implementation at startup
|
||||
static struct Injector { Injector() {
|
||||
// Do this some time before the first project is created
|
||||
(void) SampleBlockFactory::RegisterFactoryFactory(
|
||||
[]( AudacityProject &project ){
|
||||
return std::make_shared<SqliteSampleBlockFactory>( project ); }
|
||||
);
|
||||
} } injector;
|
||||
Reference in New Issue
Block a user